package com.compomics.cellmotionsimulator.gui;

import com.compomics.cellmotionsimulator.CellMotionSimulator;
import com.compomics.cellmotionsimulator.components.Cell;

import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

/**
* Created by Lennart on 30/10/2014.
*/
public class CellMotionPanel extends JPanel {

    CellMotionSimulator iCMS = null;

    private int iTopMargin = 40;

    private int iHeightMargin = 20;
    private int iWidthMargin = 20;

    private int iDiameter = 5;

    public CellMotionPanel(CellMotionSimulator aCMS) {
        iCMS = aCMS;
        // The minimum size should encompass the world size plus some buffer space.
        int height = (int)Math.round(iCMS.getWorldSize().getHeight());
        int width = (int)Math.round(iCMS.getWorldSize().getWidth());

        Dimension minimumSize = new Dimension(width+iWidthMargin, height+iHeightMargin+iTopMargin);
        this.setMinimumSize(minimumSize);
        this.setPreferredSize(minimumSize);
        this.setBackground(Color.white);
    }

    public void paint(Graphics g) {
        // First establish the size of the panel.
        int height = this.getHeight()-iHeightMargin-iTopMargin;
        int width = this.getWidth()-iWidthMargin;

        // Now determine the scale.
        double xPixelsPerUnit = ((double)width)/((double)iCMS.getWorldSize().getWidth());
        double yPixelsPerUnit = ((double)height)/((double)iCMS.getWorldSize().getHeight());

        // Plot the scale in the upper left corner.
        int yScaleCoordinate = 50 + (int)Math.round(yPixelsPerUnit);
        int xScaleCoordinate = 50 + (int)Math.round(xPixelsPerUnit);

        // Color assignment.
        Color temp = g.getColor();
        Color xColor = temp;
        Color yColor = temp;
        // Set the 'longest' axis to color 'blue'.
        if(yScaleCoordinate > xScaleCoordinate) {
            yColor = Color.blue;
            xColor = Color.red;
        } else if(xScaleCoordinate > yScaleCoordinate) {
            xColor = Color.blue;
            yColor = Color.red;
        }

        // If Y and X coordinates match up (equal pixels per unit), draw a square as well.
        if(yScaleCoordinate == xScaleCoordinate) {
            Color temp2 = g.getColor();
            g.setColor(Color.red);
            for(int i=20;i<yScaleCoordinate-10;i+=10) {
                g.drawLine(xScaleCoordinate-5, i+5, xScaleCoordinate-5, i+10);
            }
            for(int i=20;i<xScaleCoordinate-10;i+=10) {
                g.drawLine(i+5, yScaleCoordinate-5, i+10, yScaleCoordinate-5);
            }
            g.setColor(temp2);
        }

        // Y unit line.
        g.setColor(yColor);
        g.drawLine(20, 20, 20, yScaleCoordinate);
        // X unit line.
        g.setColor(xColor);
        g.drawLine(20, 20, xScaleCoordinate, 20);

        g.setColor(temp);



        // Generate a colour palette, with a colour per cell.
        Collection<Cell> cells = iCMS.getCells();
        Color[] colors = generateColors(cells.size());
        int i = 0;
        // Now cycle each cell
        for (Iterator<Cell> iterator = cells.iterator(); iterator.hasNext(); ) {
            Cell cell = iterator.next();
            // Now calculate each point.
            Collection<Point> points = calculatePoints(cell, xPixelsPerUnit, yPixelsPerUnit, iWidthMargin/2, iHeightMargin/2);

            // Plot the points.
            plotPoints(g, points, colors[i]);
            // Plot the connecting lines.
            plotLines(g, points, colors[i]);

            // Advance the colour.
            i++;
        }
    }

    /**
     * This method draws each step as a dot on the panel.
     *
     * @param aCell     Cell containing the steps to recalculate into pixel coordinates.
     * @param xScale    double with the number of pixels per unit in the horizontal direction
     * @param yScale    double with the number of pixels per unit in the vertical direction
     * @param aXMargin  int with the margin for the X coordinates.
     * @param aYMargin  int with the margin for the Y coordinates.
     * @return  Collection  with the Point instances to draw.
     */
    private Collection<Point> calculatePoints(Cell aCell, double xScale, double yScale, int aXMargin, int aYMargin) {
        Collection<Point> result = new ArrayList<Point>();

        ArrayList<Point> steps = aCell.getSteps();
        int size = steps.size();
        for (int i = 0; i < size; i++) {
            Point step = steps.get(i);
            // Make sure we break when the steps become 'null'.
            if(step == null) {
                break;
            }
            int originalX = (int)step.getX();
            int originalY = (int)step.getY();

            int newX = aXMargin + (int)Math.round(originalX*xScale);
            int newY = aYMargin + iTopMargin + (int)(originalY*yScale);

            // Create the correct coordinate point.
            result.add(new Point(newX, newY));
        }

        return result;
    }

    /**
     * This method plots the specified points on the specified Graphics object.
     *
     * @param g Graphics instance to plot the points on.
     * @param aPoints   Collection with the points to plot.
     * @param aColor    Color to draw the points in.
     */
    private void plotPoints(Graphics g, Collection<Point> aPoints, Color aColor) {
        Color original = g.getColor();
         g.setColor(aColor);

        for (Iterator<Point> iterator = aPoints.iterator(); iterator.hasNext(); ) {
            Point step = iterator.next();
            // Recalc centre to upper left corner using the radius.
            int x = (int)Math.round(step.getX() - (iDiameter/2));
            int y = (int)Math.round(step.getY() - (iDiameter/2));
            g.fillOval(x, y, iDiameter, iDiameter);
        }
        if(original != null){
            g.setColor(original);
        }
    }

    /**
     * This method plots directional arrows between the specified Points
     * on the specified Graphics object. When a location has not changed
     * between two timepoints, a red circle will be displayed there.
     *
     * @param g Graphics instance to plot the points on.
     * @param aPoints   with the points to plot.
     */
    private void plotLines(Graphics g, Collection<Point> aPoints, Color aColor) {
        Iterator<Point> iter = aPoints.iterator();
        Color original = g.getColor();
        g.setColor(aColor);

        // Only start if there's a point to draw.
        if(iter.hasNext()) {
            Point start = iter.next();
            while (iter.hasNext()) {
                Point end = iter.next();
                // The starting point.
                int firstX = (int)start.getX();
                int firstY = (int)start.getY();
                // The ending point.
                int secondX = (int)end.getX();
                int secondY = (int)end.getY();
                // Draw the line.
                g.drawLine(firstX, firstY, secondX, secondY);
                start = end;
            }
        }
        if(original != null){
            g.setColor(original);
        }
    }


    private  Color[] generateColors(int n) {
        Color[] cols = new Color[n];
        for (int i = 0; i < n; i++) {
            cols[i] = Color.getHSBColor((float) i / (float) n, 0.85f, 1.0f);
        }
        return cols;
    }
}
